Partition射影のinjected型を使ってみた
データアナリティクス事業本部の鈴木です。
Amazon Athena(以降、Athena)では、パーティション射影(Partition Projection)という機能をサポートしています。パーティション射影を使うと、Athenaはクエリを実行するパーティションを選ぶ際に、パーティション値と場所をGlueデータカタログなどのレポジトリに取りにいくのではなく、テーブル定義で設定したルールに基づいて自分で計算するようになります。これにより、クエリ実行のパフォーマンスの向上が期待できるほか、パーティション管理も自動化できます。
パーティション射影では、パーティションにenum型やinteger型などいくつかの型が設定できますが、そのうちinjected型に興味があったので調査しました。
injected型とは
パーティションが作成されているテーブルにクエリを実行する際、WHERE句でパーティションを指定すると思います。このとき、Athenaに「このパーティションを検索して欲しい!」と単一の値を指定できるパーティション列に使える型です。
詳しくは以下の公式ドキュメントをご確認ください。
injected型の利点
ユーザーがSQL内で特定のパーティションを指定できることです。Glueデータカタログなどのレポジトリにパーティション値と場所を格納・参照しないため、パーティションが非常に多くなるときでも、以下のような点を回避することができます。
- MSCK REPAIR TABLEでパーティション追加する場合に、追加するパーティションの数が非常に多いと、処理がタイムアウトになってしまうことがある。その場合、一部のパーティションがカタログに追加されないので、リトライの必要がある。
- パーティションが追加できたとしても、レポジトリに登録されるパーティションメタデータの数や、参照回数が非常に多くなってしまう。このとき、データカタログに対するパーティション取得API(GetPartitions)の呼び出しがクエリパフォーマンスのボトルネックになる場合がある。
また、以下の場合にも有効な点で、ほかの射影方法と差別化されています。
- パーティションがランダムな文字列で、ほかの射影方法を使用して射影できないときにも使える。
- パーティションが増えても定義を更新する必要がない。
試してみる
injected型のパーティションから、データを検索してみます。
今回は、温度計のようなデバイスから、データが何かしらの方法で送信されてきて、S3に保存される場合を考えてみます。S3に年月日やデバイスIDなどの規則性にしたがってデータが配置され、それをAthenaから検索するような想定です。
データは以下のcsvファイルを手作りしました。
"timestamp","temperature" 2021-05-26 01:00:00,13.6 2021-05-26 02:00:00,12.3 2021-05-26 03:00:00,12.3 2021-05-26 04:00:00,13.5 2021-05-26 05:00:00,13.6 2021-05-26 06:00:00,14.0
このcsvファイルをAthenaから参照したいバケット内に配置しておきます。
ファイルはdevice_id=xxxxxxのようなHive形式で配置してあるとします。device_idはデバイスに割り振られたIDのイメージです。このデバイスIDをパーティションに指定して、機械ごとに送られてきたデータをAthenaから検索したいとします。もし市場に膨大な数のデバイスがあるなら、同じ分だけ膨大な数のパーティションが作られることになります。
device_idの値は適当にUUIDを振っておきます。
続いて、テーブルは以下のように定義します。
CREATE EXTERNAL TABLE IF NOT EXISTS cm_suzuki_nayuta.device_data ( timestamp STRING, temperature FLOAT ) PARTITIONED BY (device_id string) ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde' LOCATION 's3://cm-nayuta-athena-practice/device_data/' TBLPROPERTIES ( "skip.header.line.count"="1", "projection.enabled" = "true", "projection.device_id.type" = "injected", "storage.location.template" = "s3://cm-nayuta-athena-practice/device_data/device_id=${device_id}" )
TBLPROPERTIESでパーティション射影を有効化し、device_idをinjected型に指定しています。
参照するcsvファイルの場所は、Amazon S3パステンプレートで設定します。具体的には"storage.location.template"の値に、csvファイルがある場所を記載します。このとき、パーティション値が入る箇所は${device_id}のようにプレースホルダにしておきます。
テーブルを作成したら、早速、以下のクエリを使って検索してみます。
SELECT * FROM device_data WHERE device_id='6deea860-95b1-b5ec-8b41-de4fa6r6ab2e';
このように、検索することができました。
injected型の制限
injected型は大変便利ですが、制限事項もあります。
記事執筆時点(2021/6/11)では、公式ドキュメントに以下の記載があります。
It is important to keep in mind the following points:
・Queries on injected columns fail if a filter expression is not provided for each injected column.
・Queries on an injected column fail if a filter expression on the column allows multiple values.
・Only columns of string type are supported.
ちょっと内容が難しかったのですが、私は以下のように理解しました。
- injected型に指定された列に対してWHERE句で指定がない場合、クエリが失敗する。
- injected型に指定された列に対してWHERE句で複数の値が指定される場合、クエリが失敗する。
- injected型に指定された列は、string型でないといけない。
文面だけだと少しイメージが付きにくかったので、実際に検証してみました。
injected型の制限の検証
WHERE句で指定がない場合
先ほど作成したテーブルに対して、以下の、WHERE句で指定がないクエリを実行してみました。
SELECT * FROM device_data
すると、「CONSTRAINT_VIOLATION: Injected projected partition column device_id must have exactly one value provided in the WHERE clause!」というエラーが出て失敗しました。
device_id列には、WHERE句で指定された値が1つだけ必要とのエラーなので、なにも指定しないとクエリが失敗することが分かりました。
WHERE句で複数の値が指定される場合
続いて、device_id列に対して、WHERE句で2つの値を指定してみました。
準備として、別のdevice_idに対応するデータをアップロードしておきます。オブジェクトキーに含まれるUUIDが異なるだけで、csvファイルは全く同じものです。
以下のようにWHERE句で複数のdevice_idを指定してSQLを実行しました。
SELECT * FROM device_data WHERE device_id='6deea860-95b1-b5ec-8b41-de4fa6r6ab2e' AND device_id='7deaa860-95a1-b5dc-8b41-de4fa666abce';
同じく、「CONSTRAINT_VIOLATION: Injected projected partition column device_id must have exactly one value provided in the WHERE clause!」というエラーが出て失敗しました。
injected型に指定した列がstring型ではない場合
最後に、injected型に指定したdevice_id列がstring型ではなかったケースを試してみます。 検証のため、以下のSQLを実行して、追加のテーブルを作成します。このテーブルと、先に作ったテーブルの違いは、PARTITIONED BYで指定したdevice_idの型がstringではなく、intであることです。
CREATE EXTERNAL TABLE IF NOT EXISTS cm_suzuki_nayuta.device_data_invalid ( timestamp STRING, temperature FLOAT ) PARTITIONED BY (device_id INT) ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde' LOCATION 's3://cm-nayuta-athena-practice/device_data/' TBLPROPERTIES ( "skip.header.line.count"="1", "projection.enabled" = "true", "projection.device_id.type" = "injected", "storage.location.template" = "s3://cm-nayuta-athena-practice/device_data/device_id=${device_id}" )
S3にcsvファイルをアップロードします。device_idは11111のようにしておきます。
準備ができたら、以下のクエリを実行してみます。
SELECT * FROM device_data_invalid WHERE device_id=11111
これも「CONSTRAINT_VIOLATION: Injected projected partition column device_id must have exactly one value provided in the WHERE clause!」というエラーが出て失敗することが分かりました。
ちなみに、string型で定義したテーブルでは、正常に動作しました。
最後に
ご紹介した例のように、パーティションの数がとても多いケースに出会うことは、しばしばあると思います。パーティション射影のinjected型は、そんなときにも強力に助けてくれます。
また、今回はinjected型のみを紹介しましたが、もちろんほかの型と組み合わせて使用することが可能です。ほかの型については、参考にあげた資料をご確認ください。injected型ならではの利点も制限もありますが、ほかの型との組み合わせつつ、Partition射影を使いこなしていきましょう。